import XLSX from "yxg-xlsx-style";
export class ExportExcel {
// 默认的表格样式
defaultCellStyle = {
  border: {
    top: { style: "thin", color: { rgb: "000000" }},
    left: { style: "thin", color: { rgb: "000000" }},
    bottom: { style: "thin", color: { rgb: "000000" }},
    right: { style: "thin", color: { rgb: "000000" }}
  },
  alignment: {
    horizontal: "center",
    vertical: "center"
  }
}
// 定义数据样式方法
styleCell = null;
/**
 * 导出
 * @param tableData 导出的数据
 * @param tableHeader excel头部
 * @param addHeadMerges 增加合并方法内没有的合并规则
 * @param delheadKey 删除合并方法内不需要的合并规则
 * @param styleCell 数据样式设置
 * @param w 列宽
 */
exportData(tableData, tableHeader, name = "表格", styleCell, w = [], addHeadMerges = [], delFun) {
  if (typeof styleCell === "function") {
    this.styleCell = styleCell;
  }

  if (!Array.isArray(tableData) || tableData.length < 1) {
    // throw { type: "error", message: "请选择需要导出的数据！" };
  }
  const sheetName = name;
  // excel表头
  const excelHeader = this.buildHeader(tableHeader);
  // 头部行数，用来固定表头
  const headerRows = excelHeader.length;
  // 提取数据
  const dataList = this.extractData(tableData, tableHeader);
  excelHeader.push(...dataList, []);

  // 计算合并
  const merges = this.doMerges(excelHeader, addHeadMerges, delFun);
  // 生成sheet
  const ws = this.aoa_to_sheet(excelHeader, headerRows, tableHeader);
  // 单元格合并
  ws["!merges"] = merges;
  // 头部冻结
  ws["!freeze"] = {
    xSplit: "1",
    ySplit: "" + headerRows,
    topLeftCell: "B" + (headerRows + 1),
    activePane: "bottomRight",
    state: "frozen"
  };
  // 列宽
  ws["!cols"] = w || [];
  const workbook = {
    SheetNames: [sheetName],
    Sheets: {}
  };
  workbook.Sheets[sheetName] = ws;
  // excel样式
  const wopts = {
    bookType: "xlsx",
    bookSST: false,
    type: "binary",
    cellStyles: true
  };

  // 表格样式;
  const wbout = XLSX.write(workbook, wopts, { defaultCellStyle: this.defaultCellStyle });
  const blob = new Blob([this.s2ab(wbout)], { type: "application/octet-stream" });
  this.openDownloadXLSXDialog(blob, sheetName + ".xlsx");

// window.saveAs(blob, sheetName + '.xlsx')
}

/**
 * 构建excel表头
 * @param revealList 列表页面展示的表头
 * @returns {[]} excel表格展示的表头
 */
buildHeader(revealList) {
  const excelHeader = [];
  // 构建生成excel表头需要的数据结构
  this.getHeader(revealList, excelHeader, 0, 0);
  // 多行表头长短不一，短的向长的看齐，不够的补上行合并占位符
  const max = Math.max(...excelHeader.map((a) => a.length));
  excelHeader.filter((e) => e.length < max).forEach((e) => this.pushRowSpanPlaceHolder(e, max - e.length));
  return excelHeader;
}

/**
 * 生成头部
 * @param headers 展示的头部
 * @param excelHeader excel头部
 * @param deep 深度
 * @param perOffset 前置偏移量
 * @returns {number}  后置偏移量
 */
getHeader(headers, excelHeader, deep, perOffset) {
  let offset = 0;
  let cur = excelHeader[deep];
  if (!cur) {
    cur = excelHeader[deep] = [];
  }
  // 填充行合并占位符
  this.pushRowSpanPlaceHolder(cur, perOffset - cur.length);
  for (let i = 0; i < headers.length; i++) {
    const head = headers[i];
    cur.push(head.label);
    if (head?.childern && Array.isArray(head.childern) && head.childern.length > 0) {
      const childOffset = this.getHeader(head.childern, excelHeader, deep + 1, cur.length - 1);
      // 填充列合并占位符
      this.pushColSpanPlaceHolder(cur, childOffset - 1);
      offset += childOffset;
    } else {
      offset++;
    }
  }
  return offset;
}

/**
 * 根据选中的数据和展示的列，生成结果
 * @param selectionData
 * @param revealList
 */
extractData(selectionData, revealList) {
// 列
  const headerList = this.flat(revealList);
  // 导出的结果集
  const excelRows = [];
  // 如果有child集合的话会用到
  const dataKeys = new Set(Object.keys(selectionData[0]));
  selectionData.some((e) => {
    if (e.childern && e.childern.length > 0) {
      const childKeys = Object.keys(e.childern[0]);
      for (let i = 0; i < childKeys.length; i++) {
        dataKeys.delete(childKeys[i]);
      }
      return true;
    }
  });
  this.flatData(selectionData, (list) => {
    excelRows.push(...this.buildExcelRow(dataKeys, headerList, list));
  });
  return excelRows;
}

buildExcelRow(mainKeys, headers, rawDataList) {
// 合计行
  const sumCols = [];
  // 数据行
  const rows = [];
  for (let i = 0; i < rawDataList.length; i++) {
    const cols = [];
    const rawData = rawDataList[i];
    // 提取数据
    for (let j = 0; j < headers.length; j++) {
      const header = headers[j];
      // 父元素键需要行合并
      if (rawData["rowSpan"] === 0 && mainKeys.has(header.prop)) {
        cols.push("!$ROW_SPAN_PLACEHOLDER");
      } else {
        let value;
        if (typeof header.exeFun === "function") {
          value = header.exeFun(rawData);
        } else {
          value = rawData[header.prop];
        }
        cols.push(value);
        // 如果该列需要合计,并且是数字类型
        if (header["summable"] && typeof value === "number") {
          sumCols[j] = (sumCols[j] ? sumCols[j] : 0) + value;
        }
      }
    }
    rows.push(cols);
  }
  // 如果有合计行
  if (sumCols.length > 0) {
    rows.push(...this.sumRowHandle(sumCols));
  }
  return rows;
}
sumRowHandle(sumCols) {
// TODO
  return [];
}

/**
 * @description: 需要合并的数据
 * @param {*} arr
 * @return {*}
 */
doMerges(arr, addArr, delFun) {
// 要么横向合并 要么纵向合并
  const deep = arr.length;
  let merges = [];
  for (let y = 0; y < deep; y++) {
    // 先处理横向合并
    const row = arr[y];
    let colSpan = 0;
    for (let x = 0; x < row.length; x++) {
      if (row[x] === "!$COL_SPAN_PLACEHOLDER") {
        row[x] = undefined;
        if (x + 1 === row.length) {
          merges.push({ s: { r: y, c: x - colSpan - 1 }, e: { r: y, c: x }});
        }
        colSpan++;
      } else if (colSpan > 0 && x > colSpan) {
        merges.push({ s: { r: y, c: x - colSpan - 1 }, e: { r: y, c: x - 1 }});
        colSpan = 0;
      } else {
        colSpan = 0;
      }
    }
  }
  // 再处理纵向合并
  const colLength = arr[0].length;
  for (let x = 0; x < colLength; x++) {
    let rowSpan = 0;
    for (let y = 0; y < deep; y++) {
      if (arr[y][x] === "!$ROW_SPAN_PLACEHOLDER") {
        arr[y][x] = undefined;
        if (y + 1 === deep) {
          merges.push({ s: { r: y - rowSpan, c: x }, e: { r: y, c: x }});
        }
        rowSpan++;
      } else if (rowSpan > 0 && y > rowSpan) {
        merges.push({ s: { r: y - rowSpan - 1, c: x }, e: { r: y - 1, c: x }});
        rowSpan = 0;
      } else {
        rowSpan = 0;
      }
    }
  }

  // 直接添加合并规则
  if (addArr.length) {
    merges.push(...addArr);
  }

  // 直接删除不需要的合并规则
  if (typeof delFun === "function") {
    merges = delFun(merges);
  }
  console.log(merges, "merges后");
  return merges;
}

/**
* 从github复制过来的
*/
aoa_to_sheet(data, headerRows) {
  const ws = {};
  const range = { s: { c: 10000000, r: 10000000 }, e: { c: 0, r: 0 }};
  for (let R = 0; R !== data.length; ++R) {
    for (let C = 0; C !== data[R].length; ++C) {
      if (range.s.r > R) {
        range.s.r = R;
      }
      if (range.s.c > C) {
        range.s.c = C;
      }
      if (range.e.r < R) {
        range.e.r = R;
      }
      if (range.e.c < C) {
        range.e.c = C;
      }
      // / 这里生成cell的时候，使用上面定义的默认样式
      const cell = {
        v: data[R][C] || "",
        s: Object.assign({}, this.defaultCellStyle) // 这里是因为防止浅复制导致数据地址指向同一数据
      };

      // 头部列表加边框
      if (R < headerRows) {
        const style = Object.assign(cell.s, {

          border: {
            top: { style: "thin", color: { rgb: "000000" }},
            left: { style: "thin", color: { rgb: "000000" }},
            bottom: { style: "thin", color: { rgb: "000000" }},
            right: { style: "thin", color: { rgb: "000000" }}
          },
          font: {
            sz: 12,
            bold: true
          },
          fill: {
            patternType: "solid",
            fgColor: { theme: 3, tint: 0.3999755851924192, rgb: "F5F7FA" },
            bgColor: { theme: 7, tint: 0.3999755851924192, rgb: "ffff00" }
          }
        });
        cell.s = style;
      }
      const cell_ref = XLSX.utils.encode_cell({ c: C, r: R });
      if (typeof cell.v === "number") {
        cell.t = "n";
      } else if (typeof cell.v === "boolean") {
        cell.t = "b";
      } else {
        cell.t = "s";
      }

      // 插入表格样式
      if (typeof this.styleCell === "function") {
        // 插入数据样式
        const styleCell = this.styleCell(R, C, cell);
        if (typeof styleCell === "object") {
          cell.s = Object.assign(cell.s, styleCell);
        }
      }

      ws[cell_ref] = cell;
    }
  }
  if (range.s.c < 10000000) {
    ws["!ref"] = XLSX.utils.encode_range(range);
  }
  return ws;
}
/**
* 填充行合并占位符
* */
pushRowSpanPlaceHolder(arr, count) {
  for (let i = 0; i < count; i++) {
    arr.push("!$ROW_SPAN_PLACEHOLDER");
  }
}
// 填充列合并占位符
pushColSpanPlaceHolder(arr, count) {
  for (let i = 0; i < count; i++) {
    arr.push("!$COL_SPAN_PLACEHOLDER");
  }
}
/**
* @description:铺平数组
* @param {*} list 表格数据
* @param {*} eachDataCallBack 构建的行数据
* @return {*}
*/
flatData(list, eachDataCallBack) {
  const resultList = [];
  for (let i = 0; i < list.length; i++) {
    const data = list[i];
    const rawDataList = [];
    // 每个子元素都和父元素合并成一条数据
    if (data.childern && data.childern.length > 0) {
      for (let j = 0; j < data.childern.length; j++) {
        delete data.childern[j].bsm;
        const copy = Object.assign({}, data, data.childern[j]);
        rawDataList.push(copy);
        copy["rowSpan"] = j > 0 ? 0 : data.childern.length;
      }
    } else {
      data["rowSpan"] = 1;
      rawDataList.push(data);
    }
    resultList.push(...rawDataList);
    if (typeof eachDataCallBack === "function") {
      eachDataCallBack(rawDataList);
    }
  }
  return resultList;
}
// 扁平头部
flat(revealList) {
  const result = [];
  revealList.forEach((e) => {
    if (e?.childern) {
      result.push(...this.flat(e.childern));
    } else if (e?.exeFun) {
      result.push(e);
    } else if (e?.prop) {
      result.push(e);
    }
  });
  return result;
}
s2ab(s) {
  const buf = new ArrayBuffer(s.length);
  const view = new Uint8Array(buf);
  for (let i = 0; i !== s.length; ++i) {
    view[i] = s.charCodeAt(i) & 0xff;
  }
  return buf;
}
openDownloadXLSXDialog(url, saveName) {
  if (typeof url === "object" && url instanceof Blob) {
    url = URL.createObjectURL(url); // 创建blob地址
  }
  var aLink = document.createElement("a");
  aLink.href = url;
  aLink.download = saveName || ""; // HTML5新增的属性，指定保存文件名，可以不要后缀，注意，file:///模式下不会生效
  var event;
  if (window.MouseEvent) {
    event = new MouseEvent("click");
  } else {
    event = document.createEvent("MouseEvents");
    event.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
  }
  aLink.dispatchEvent(event);
}
}
